package com.beanFactory;

import com.remedios.myAnnotation.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author 并不自律但是残忍
 */
public final class MyAnnotationConfigApplicationContext {
    static Map<Class<?>, Object> beans = new LinkedHashMap<>();
    static HashSet<String> propList = new HashSet<>();
    static Properties props = new Properties();
    static String packagePath;
    static String parentPath;
    //私有构造
    private MyAnnotationConfigApplicationContext() {
    }

    static {
//        SAXReader saxReader = new SAXReader();
        File checkPackage = new File(Thread.currentThread().getContextClassLoader().getResource("").getPath());
        getPackageName(checkPackage);
//        纯注解 不用读取配置文件中的信息了 所以读取配置文件的不需要了
        parentPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //获取需要初始化bean的path
        packagePath = parentPath + packagePath.replace(".", "/");
        //根据path创建File对象
        File file = new File(packagePath);
        //获取指定包下所有类对象的全类名
        getClass(file);
        //读取配置文件
        getProperties();
        //自动注入
        autoWired();
    }
    //判断是否有配置类且配置类是否指定了要初始化bean的包名  必须配置配置类和扫描MyComponentScan指定初始化bean的包名才可以正常运行 否则报错
    private static void getPackageName(File file) {
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.isDirectory()) {
                getPackageName(file1);
            } else {
                if (file1.getName().endsWith(".class")) {
                    String absolutePath = file1.getAbsolutePath();
                    String classes = absolutePath.split("classes")[1];
                    classes = classes.substring(1);
                    classes = classes.replace("\\", ".");
                    classes = classes.substring(0, classes.lastIndexOf("."));
                    try {
                        Class<?> aClass = Class.forName(classes);

                        //是注解配置类 并且配置了包扫描注解 则初始化bean 否则失败
                        if (aClass.isAnnotationPresent(MyConfiguration.class) && aClass.isAnnotationPresent(MyComponentScan.class)) {
                            //获取扫描注所指定的包
                            packagePath = aClass.getAnnotation(MyComponentScan.class).value();
                            return;
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    //获取指定包下所有类对象的全类名
    private static void getClass(File file) {
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.isDirectory()) {
                getClass(file1);
            } else if (file1.getName().endsWith(".class")) {
                String path = file + "\\" + file1.getName().replace(".class", "");
                path = path.split("classes")[1];
                path = path.replace("\\", ".").substring(1);
                //判断类对象是否有MYComponent MYController MYService MYRepository四种注解
                checkAnnotation(path);
            }
        }
    }

    //判断类对象是否有MYComponent MYController MYService MYRepository四种注解 有创建对象放入集合
    private static void checkAnnotation(String path) {
        try {
            //反射根据全类名创建对象
            Class<?> aClass = Class.forName(path);
            //检查注解
            if (aClass.isAnnotationPresent(MyComponent.class) ||
                    aClass.isAnnotationPresent(MyService.class) ||
                    aClass.isAnnotationPresent(MyController.class) ||
                    aClass.isAnnotationPresent(MyRepository.class)) {
                //加了注解就创建对象 存到beans集合中 key为全类名 value是对象
                Object obj = aClass.newInstance();
                beans.put(aClass, obj);
                //如果这是一个实现类,则找出其实现的接口 用接口的全类名做key再存一份实现类对象 这样可以使用接口的全类名来获取实现类对象
                Class<?>[] interfaces = aClass.getInterfaces();
                if (interfaces != null && interfaces.length > 0) {
                    for (Class<?> anInterface : interfaces) {
                        beans.put(anInterface, obj);
                    }
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    //获取当前模块下resources下所有的properties文件
    private static void getProperties() {
        String path = MyAnnotationConfigApplicationContext.class.getClassLoader().getResource("").getPath();
        path = path.split("target")[0];
        path = path + "src/main/resources";
        File file = new File(path);
        File[] files = file.listFiles();
        for (File file1 : files) {
            if (file1.getName().endsWith("properties")) {
                //每找到一个文件就把其名字存入set集合中
                propList.add(file1.getName());
            }
        }
    }

    //获取bean对象
    public static Object getBean(Class<?> clazz) {
        if (beans.get(clazz) != null) {
            return beans.get(clazz);
        }
        return null;
    }

    //自动注入
    private static void autoWired() {
        //循环遍历整个beans集合
        beans.forEach((clazz, obj) -> {
            //拿到每个类对象中的成员属性数组
            Field[] declaredFields = clazz.getDeclaredFields();
            //遍历成员属性数组
            for (Field field : declaredFields) {
                field.setAccessible(true);
                //判断成员属性上是否有自动注入的注解
                if (field.isAnnotationPresent(MyAutoWired.class)) {
                    // 目前只做到int double和String类用value赋值
                    // 引用数据类型类 接口 只能自动注入当前模块下存在的类 集合数组等无法注入
                    if (field.isAnnotationPresent(MyValue.class)) {
                        String value = field.getAnnotation(MyValue.class).value();
                        //判断是用占位符指向properties文件中的数值还是value直接赋值
                        if (value.startsWith("${") && value.endsWith("}")) {
                            value = value.substring(2);
                            value = value.substring(0, value.length() - 1);
                            String propName = value.split("\\.")[0];
                            String propValue = value.split("\\.")[1];
                            propName = propName + ".properties";
                            readProp(propName, propValue, obj, field);
                        } else {
                            try {
                                if (field.getType().equals(String.class)) {
                                    field.set(obj, value);
                                } else if (field.getType().equals(Integer.class)) {
                                    int i = Integer.parseInt(value);
                                    field.set(obj, i);
                                } else if (field.getType().equals(Double.class)) {
                                    double d = Double.parseDouble(value);
                                    field.set(obj, d);
                                }else if(field.getType().equals(Long.class)){
                                    long l = Long.parseLong(value);
                                    field.set(obj, l);
                                }else if(field.getType().equals(Boolean.class)){
                                    boolean b = Boolean.parseBoolean(value);
                                    field.set(obj, b);
                                } else {
                                    field.set(obj, value);
                                }
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            }
                        }
                        //是类或者接口的情况
                    } else {
                        String fieldName = field.getType().getName();
                        try {
                            //看看集合中有没有这个类或者接口对应的对象
                            if (beans.containsKey(Class.forName(fieldName))) {
                                field.set(obj, beans.get(Class.forName(fieldName)));
                            }
                        } catch (ClassNotFoundException | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }

    private static void readProp(String propName, String annoValue, Object obj, Field field) {
        for (String prop : propList) {
            field.setAccessible(true);
            //如果成员属性使用占位符指向了当前模块下的.properties文件
            if (propName.equals(prop)) {
                //读取这个文件
                InputStream ras = MyAnnotationConfigApplicationContext.class.getClassLoader().getResourceAsStream(propName);
                try {
                    //将文件中的内容读取到Properties集合中
                    props.load(ras);
                    //获取这个成员属性在文件中的key所对应的value值
                    String property = props.getProperty(annoValue);

                    //String value = new String(props.getProperty(annoValue).getBytes("ISO-8859-1"), "utf-8");

                    //再次判断下数值类型 然后根据类型注入属性
                    if (field.getType().equals(String.class)) {
                        field.set(obj, property);
                    } else if (field.getType().equals(Integer.class)) {
                        int i = Integer.parseInt(property);
                        field.set(obj, i);
                    } else if (field.getType().equals(Double.class)) {
                        double d = Double.parseDouble(property);
                        field.set(obj, d);
                    }else if(field.getType().equals(Long.class)){
                        long l = Long.parseLong(property);
                        field.set(obj, l);
                    }else if(field.getType().equals(Boolean.class)){
                        boolean b = Boolean.parseBoolean(property);
                        field.set(obj, b);
                    }
                    //注入完成后清空这个Properties集合 为其他类的属性注入做准备
                    props.clear();
                    break;
                } catch (IOException | IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
